﻿using System;
using System.Net;


using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Configuration;
using System.IO;


namespace Tulde.Windows.Music.Mp3Tunes
{
    /// <summary>
    /// A base class implementation of IDataService.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public abstract class BaseDataService<T> : IDataService<T>
    {
        /// <summary>
        /// BaseUrl pattern for an endpoint.
        /// </summary>
        protected abstract string BaseUrl { get; }


        #region IDataService<T> Members
        /// <summary>
        /// Creates a Uri based on an operation and ServiceParameters.
        /// </summary>
        /// <param name="operation">An enumeration type.</param>
        /// <param name="parameters">ServiceParameter instance.</param>
        /// <returns></returns>
        public virtual Uri CreateServiceEndpoint(T operation, ServiceParameters parameters)
        {
            return new Uri(this.AppendParametersToUrl(this.BaseUrl, operation.ToString(), parameters));
        }
        /// <summary>
        /// Downloads data from a Uri asynchronously.  If the data already exists in cache, then reads the data from cache.
        /// </summary>
        /// <typeparam name="T1">The generic type representing the deserialized ViewModel</typeparam>
        /// <param name="uri">The url to invoke</param>
        /// <param name="callback">Callback lamba</param>
        public virtual void InvokeAsynch<T1>(Uri uri, Action<T1> callback)
        {
            if (ViewModelCacheFactory.Instance.CacheItemExists(uri.OriginalString))
            {
                callback(ViewModelCacheFactory.Instance.GetCacheItem<T1>(uri.OriginalString));
            }
            else
            {
                System.Net.WebClient authClient = new WebClient();
                authClient.DownloadStringCompleted += new DownloadStringCompletedEventHandler(DataService_DownloadStringCompleted);
                authClient.DownloadStringAsync(uri, new Tuple<object,object>( uri,callback));
            }
        }
        /// <summary>
        /// Abstract method that handles deserialization of the JSON result.  
        /// </summary>
        /// <param name="result">JSON string</param>
        /// <returns>an instance of a view model object</returns>
        protected abstract object DeserializeResult(string result);
        /// <summary>
        /// Called when the asynchronous operation completes.  This method attempts to deserialize the result and invoke the callback lamba.
        /// This method also attempts to cache data using the registered IViewModelCacheProvider.
        /// If you wish to debug the downloaded JSON, add a a key "debug" to appSettings in your config file and set it to true.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected virtual void DataService_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {
            if (e.UserState != null)
            {
                string debug = ConfigurationManager.AppSettings["debug"];
                if (!String.IsNullOrEmpty(debug) && String.Compare(debug, "true", true) == 0)
                {
                    using (StreamWriter w = new StreamWriter("JSON.txt", true))
                    {
                        w.WriteLine(e.Result.Replace("}", "}" + Environment.NewLine));
                        w.Close();
                    }
                }
                object instance = this.DeserializeResult(e.Result);
                Tuple<object,object> state = (Tuple<object,object>)e.UserState;
                object action = state.Item2;

             
                Uri uri = state.Item1 as Uri;
                Type type = action.GetType();
                MethodInfo method = type.GetMethod("Invoke");
                //LateBoundMethod callback = DelegateFactory.Create(method);

               
               
                
                if (instance is ICacheable)
                {
                    if (((ICacheable)instance).ShouldCache)
                    {
                        ViewModelCacheFactory.Instance.SetCacheItem(uri.OriginalString, instance);
                    }
                }
                //else
                //{
                //    ViewModelCacheFactory.Instance.SetCacheItem(uri.OriginalString, instance);
                //}
                //callback.Invoke(action, new object[] { instance });
               method.Invoke(action, new object[] { instance});
             



            }
        }
        /// <summary>
        /// Formats the ServiceParameters instance as url parameters and appends them to the Url.
        /// </summary>
        /// <param name="url"></param>
        /// <param name="operation"></param>
        /// <param name="parameters"></param>
        /// <returns></returns>
        protected virtual string AppendParametersToUrl(string url, string operation, ServiceParameters parameters)
        {
            List<string> parametersList = new List<string>();
            foreach (string key in parameters.Keys)
            {
                parametersList.Add(String.Format("{0}={1}", key, parameters.GetParameter(key)));
            }
            if (parameters == null || !parameters.Keys.Any())
            {
                return url;
            }
            else
            {
                return url + operation + "?" + String.Join("&", parametersList.ToArray());
            }
        }

        #endregion
    }
    //public delegate void LateBoundMethod(object target, object[] arguments);

    //public static class DelegateFactory
    //{
    //    public static LateBoundMethod Create(MethodInfo method)
    //    {
    //        ParameterExpression instanceParameter = Expression.Parameter(typeof(object), "target");
    //        ParameterExpression argumentsParameter = Expression.Parameter(typeof(object[]), "arguments");

           
    //        MethodCallExpression call = Expression.Call(
    //          Expression.Convert(instanceParameter, method.DeclaringType),
    //          method,
    //          CreateParameterExpressions(method, argumentsParameter));

          
    //        Expression<LateBoundMethod> lambda = Expression.Lambda<LateBoundMethod>(
    //          Expression.Empty(),
    //          instanceParameter,
    //          argumentsParameter);

    //        return lambda.Compile();
    //    }

    //    private static Expression[] CreateParameterExpressions(MethodInfo method, Expression argumentsParameter)
    //    {
    //        return method.GetParameters().Select((parameter, index) =>
    //          Expression.Convert(
    //            Expression.ArrayIndex(argumentsParameter, Expression.Constant(index)), parameter.ParameterType)).ToArray();
    //    }
    //}
}
